﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.Shell.Implementation
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows.Forms;
    using ConnectBasic.Shell.Extensibility;
    using ConnectDebug;

    internal partial class MacroIdeForm : Form
    {
        private DefaultShell mShell;
        private IDebugger mDebugger;

        #region .ctor + Initialization

        internal MacroIdeForm()
        {
            InitializeComponent();
        }

        internal void Initialize(DefaultShell shell)
        {
            Debug.Assert(shell != null);
            mShell = shell;

            // Get services
            mDebugger = mShell.GetService<IDebuggerService>().Debugger;
            mDebugger.BreakpointHit += new EventHandler(mDebugger_BreakpointHit);

            // Initialize the UI
            InitializeToolWindows(mShell);
        }

        internal void InitializeToolWindows(IShell shell)
        {
            var zones = shell.ToolWindowZones;

            // TODO: Dynamic zones
            LeftTopZone.Initialize(shell, zones, "LeftTop");
            LeftBottomZone.Initialize(shell, zones, "LeftBottom");
            RightBottomZone.Initialize(shell, zones, "RightBottom");
        }

        #endregion

        #region Debugger Event Handlers

        private IHighlightService mCurrentHighlightService;
        private int mCurrentHighlightServiceId;

        void mDebugger_BreakpointHit(object sender, System.EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new EventHandler(mDebugger_BreakpointHit), sender, e);
            }
            else
            {

                // Get the editor for the break point
                if (!string.Equals(TheEditorZone.ActiveEditorFileName, mDebugger.CurrentPosition.FileName, StringComparison.Ordinal))
                {
                    var documentQuery = from project in mShell.Projects
                                        from item in project.Items
                                        where item is IEditable && item is ICodeDocument && string.Equals(((ICodeDocument)item).FileName, mDebugger.CurrentPosition.FileName)
                                        select (IEditable)item;

                    var documents = documentQuery.ToArray();
                    Debug.Assert(documents.Length == 0);

                    bool editResult = documents[0].TryEdit();
                    Debug.Assert(editResult);
                }

                // If we've already got a highlight, clear it
                if (mCurrentHighlightService != null)
                {
                    mCurrentHighlightService.ClearHighlight(mCurrentHighlightServiceId);
                }

                // Now the editor is active, try highlighting the current
                // line
                mCurrentHighlightService = TheEditorZone.ActiveEditorServiceProvider.GetService<IHighlightService>();

                if (mCurrentHighlightService != null)
                {
                    var span = mDebugger.CurrentPosition;

                    mCurrentHighlightServiceId =
                        mCurrentHighlightService.SetHighlight(
                            "CurrentLine",
                            span.StartLine,
                            span.StartColumn,
                            span.EndLine,
                            span.EndColumn);                        
                }

                // Enable UI related to waiting for a user
                // debugging action
                DebugWaitForUserAction(true);
            }
        }

        #endregion

        #region UI Event Handlers

        private void MacroIdeForm_FormClosing(object sender, FormClosingEventArgs e)
        {

            // Make sure that if the user closes the form, we don't get destroyed
            // because the user may well want to open it again
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                this.Hide();
            }
        }

        private void ExecuteButton_Click(object sender, System.EventArgs e)
        {

            // Either continue execution, or start a new macro
            if (mDebugger.InBreak)
            {

                // Disable UI related to waiting for a user
                // debugging action
                DebugWaitForUserAction(false);

                // We're in a break, continue execution
                mDebugger.Continue();
            }
            else
            {

                // New Macro
                var macro = PromptForMacro();

                if (macro != null)
                {
                    RunMacro(macro);
                }
            }
        }

        private void StepOverButton_Click(object sender, System.EventArgs e)
        {

            // Either continue execution, or start a new macro
            if (mDebugger.InBreak)
            {

                // Disable UI related to waiting for a user
                // debugging action
                DebugWaitForUserAction(false);

                // We're in a break, just use the step-over method
                mDebugger.StepOver();
            }
            else
            {

                // New Macro
                var macro = PromptForMacro();

                if (macro != null)
                {

                    // Tell the debugger to wait as soon as we start
                    mDebugger.Pause();

                    // Go for it
                    RunMacro(macro);
                }
            }
        }

        private void StepIntoButton_Click(object sender, System.EventArgs e)
        {

            // Either continue execution, or start a new macro
            if (mDebugger.InBreak)
            {

                // Disable UI related to waiting for a user
                // debugging action
                DebugWaitForUserAction(false);

                // We're in a break, just use the step-into method
                mDebugger.StepInto();
            }
            else
            {

                // New Macro
                var macro = PromptForMacro();

                if (macro != null)
                {

                    // Tell the debugger to wait as soon as we start
                    mDebugger.Pause();

                    // Go for it
                    RunMacro(macro);
                }
            }
        }

        private void StepOutButton_Click(object sender, System.EventArgs e)
        {
            Debug.Assert(mDebugger.InBreak);

            // Disable UI related to waiting for a user
            // debugging action
            DebugWaitForUserAction(false);

            // Step-out
            mDebugger.StepOut();
        }

        #endregion

        #region Helpers

        private void DebugWaitForUserAction(bool waiting)
        {
            StepOutButton.Enabled = waiting;
        }

        private void RunMacro(IExecutableMacro macro)
        {

            // Let the shell update its state in response
            // to the macro starting/stopping
            mShell.SetActiveMacro(macro);

            // Make sure we know when the macro completes
            macro.Finished += new EventHandler(MacroCleanup);

            // Go for it!
            macro.Execute();
        }

        void MacroCleanup(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new EventHandler(MacroCleanup), sender, e);
            }
            else
            {
                // Make sure we don't hang on to the object
                ((IExecutableMacro)sender).Finished -= MacroCleanup;

                // If there's an editor with a highlight, remove it
                if (mCurrentHighlightService != null)
                {
                    mCurrentHighlightService.ClearHighlight(mCurrentHighlightServiceId);
                }
            }
        }

        private IExecutableMacro PromptForMacro()
        {

            // Find macros in all projects
            var macroQuery = from project in mShell.Projects
                             from item in project.Items
                             where item is IMacroExecutionService
                             from macro in ((IMacroExecutionService)item).GetExecutableMacros()
                             select macro;
            var macros = macroQuery.ToArray();

            // Make sure there are some macros to select
            if (macros.Length > 0)
            {
                using (var dlg = new MacroSelectForm())
                {
                    dlg.MacroList.Items.AddRange(macros);

                    if (dlg.ShowDialog() == DialogResult.OK)
                    {
                        return ((IExecutableMacro)dlg.MacroList.SelectedItem);
                    }
                }
            }

            // If we get here, either there are no macros, or the user cancelled
            return null;
        }

        #endregion

    }
}
